﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Linq;
using PlottingBasics.LINQHelpers;
using PlottingBasics.Functions;

namespace PlottingBasics.PlotManipulations
{
    /// <summary>
    /// Some enumerable helpers for dealing with sequences of points.
    /// </summary>
    public static class SequenceHelpers
    {
        /// <summary>
        /// Info about where the division occurs.
        /// </summary>
        public struct BetweenInfo
        {
            public double yValue;
            public double xValue;
            public Point lowPoint;
            public Point highPoint;
            public double fractionBetween;
        }

        /// <summary>
        /// Find the first spot in this sequence where value is between two Y values. Return the
        /// interpolated X value where that happens, along with a bunch of info about that to
        /// help with later calculations.
        /// </summary>
        /// <param name="inputSequence"></param>
        /// <param name="yValue"></param>
        /// <returns></returns>
        public static BetweenInfo XPosOfYValue(this IEnumerable<Point> inputSequence,
            double yValue)
        {
            ///
            /// Find where we should be making the cut
            /// 

            BetweenInfo result;
            var betweenGuys = inputSequence.AdjacentItems(yValue, a => a.Y);
            result.lowPoint = betweenGuys._first;
            result.highPoint = betweenGuys._second;
            result.yValue = yValue;
            result.fractionBetween = 0.0;

            ///
            /// Special cases, which happen sometimes! Yuck!
            ///  - If there is no cut before the first one, then we return the whole thing
            ///  - if it is after the last one (weird!) it is all flat!
            /// 

            if (betweenGuys._first == default(Point)
                && betweenGuys._second == default(Point)
                && !inputSequence.Any())
            {
                throw new ArgumentException("Empty sequence - can't figure out how to calc remaining probability");
            }

            if (betweenGuys._first == default(Point)
                && betweenGuys._second == inputSequence.First())
            {
                // We are before the first left edge
                result.xValue = inputSequence.First().X;
                return result;
            }

            if (betweenGuys._second == default(Point)
                && betweenGuys._first == inputSequence.Last())
            {
                // We are after the left edge
                result.xValue = inputSequence.Last().X;
                return result;
            }

            ///
            /// Now, find the actual x value where this happens.
            /// 

            result.fractionBetween = Helpers.FractionBetween(betweenGuys._first.Y, betweenGuys._second.Y, yValue);
            result.xValue = Helpers.CalcFractionBetween(betweenGuys._first.X, betweenGuys._second.X, result.fractionBetween);

            return result;
        }

        /// <summary>
        /// This will take a given curve and flatten it up to a point x. x can be inbetween two actual
        /// points we know and with the info provided will do proper (linear) interpolation.
        /// </summary>
        /// <param name="input">The sequence we will be returning, flattened</param>
        /// <param name="xTurnOn">The xposition where this "flattening" should occur</param>
        /// <returns></returns>
        public static IEnumerable<Point> FlattenCurveAtPositionInterpolated(this IEnumerable<Point> input,
            double xTurnOn)
        {
            ///
            /// Special cases, which happen sometimes! Yuck!
            ///  - If there is no cut before the first one, then we return the whole thing
            ///  - if it is after the last one (weird!) it is all flat!
            /// 

            if (xTurnOn == input.First().X)
            {
                return input;
            }

            if (xTurnOn == input.Last().X)
            {
                return new Point[] {
                    new Point (input.First().X, 0.0),
                    new Point (input.Last().X, 0.0)
                };
            }

            ///
            /// Ok - we now need to put together a new sequence. Find the fractional distance between where the cut happens.
            /// 

            Point p1 = (from p in input where p.X < xTurnOn select p).Last();
            Point p2 = (from p in input where p.X > xTurnOn select p).First();

            double fraction = Helpers.FractionBetween(p1.X, p2.X, xTurnOn);
            double yinterp = Helpers.CalcFractionBetween(p1.Y, p2.Y, fraction);

            ///
            /// Ok, now we need to put together a new sequence. Things are a bit funny since we hvae to deal with
            /// three sequences here, before the transition, the transition itself, and after the transition.
            /// 

            Point[] firstPart = new Point[] {
                new Point(input.First().X, 0.0),
                new Point(xTurnOn*0.999, 0.0),
                new Point(xTurnOn, yinterp)
            };

            double turnOn = xTurnOn;
            var pointsPastX = from p in input
                              where p.X >= turnOn
                              select p;

            return firstPart.Concat(pointsPastX);
        }
    }
}
